﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AlgoImpl
{
    public class Graph
    {
        private List<Node> m_Nodes = new List<Node>();
        private Node m_Source;
        private Node m_Target;
        // Singelton Attr
        private static volatile Graph s_Instance = null;
        private static object s_LockObj = new Object();

        public static Graph Instance
        {
            get
            {
                if (s_Instance == null)
                {
                    lock (s_LockObj)
                    {
                        if (s_Instance == null)
                        {
                            s_Instance = new Graph();
                        }

                    }
                }

                return s_Instance;
            }
        }

        private Graph(){ }

        private void createTwoSidedGraph()
        {
            m_Source = new Node("s", eSideInGraph.SOURCE);
            m_Target = new Node("t", eSideInGraph.DEST);
        }

        private bool isNodeAmdNeighbourHaveDifferentSides(string nodeId, string neighbourId)
        {
            bool toReturn = false;
            if (this[nodeId].Side.Equals(eSideInGraph.LEFT) && this[neighbourId].Side.Equals(eSideInGraph.RIGHT))
            {
                toReturn = true;
            }
            else
            {
                // throw cannot connenct between nodes in same side of graph
            }
            return toReturn;
        }

        public void MakeEmpty()
        {
            m_Nodes.Clear();
            createTwoSidedGraph();
        }

        public List<Node> Nodes 
        {
            get { return m_Nodes; }
            set { m_Nodes = value; } 
        }

        public Node Source
        {
            get { return m_Source; }
            set { m_Source = value; } 
        }

        public Node Target
        {
            get { return m_Target; }
            set { m_Target = value; }
        }

        public Node this[string id]
        {
            get 
            {
                if (id.Equals("s"))
                {
                    return m_Source;
                }
                else if (id.Equals("t"))
                {
                    return m_Target;
                }
                else
                {
                    return m_Nodes[int.Parse(id)]; 
                }
                
            }
            set 
            {
                m_Nodes[int.Parse(id)] = value;
            }
        }

        public List<DataNode> GetAllNodesFromSide(eSideInGraph side)
        {
            List<DataNode> listOfOneSide = new List<DataNode>();
            foreach (Node node in m_Nodes)
            {
                if (node.Side.Equals(side))
                {
                    listOfOneSide.Add(node as DataNode);
                }
            }
            return listOfOneSide;
        }

        public void AddNodeToSide(string Data, string id, eSideInGraph side)
        {
            if (side.Equals(eSideInGraph.LEFT))
            {
                m_Nodes.Add(new DataNode(id, eSideInGraph.LEFT, Data));
                m_Source.AddNeighbour(id, 1);
            }
            else if (side.Equals(eSideInGraph.RIGHT))
            {
                m_Nodes.Add(new DataNode(id, eSideInGraph.RIGHT, Data));
                this[id].AddNeighbour("t", SharedData.Instance.GetShiftById(Data).MaxNumberOfShifts);
            }
        }

        public void AddNeighbourToNode(string nodeId, string neighbourId, int weight)
        {
            if (isNodeAmdNeighbourHaveDifferentSides(nodeId, neighbourId))
            {
                this[nodeId].AddNeighbour(neighbourId, weight);
            }
        }

        public Node FindNode(string nodeId)
        {
            foreach (Node node in m_Nodes)
            {
                if (node.Id.Equals(nodeId))
                {
                    return node;
                }
            }
            return null;
        }

        public string GetNodeIdByDataId(string nodeId)
        {
            string idOfNode = string.Empty;
            foreach (DataNode node in m_Nodes)
            {
                if (node.Data.Equals(nodeId))
                {
                    idOfNode = node.Id;
                    break;
                }
            }
            return idOfNode;
        }

        public override string ToString()
        {
            string tostring = string.Empty;
            tostring += m_Source.ToString();
            foreach (Node node in m_Nodes)
            {
                tostring += node.ToString();
            }
            tostring += m_Target.ToString();
            return tostring;
        }

        public void InitLeftAndRightNodes()
        {
            for (int i = 0; i < m_Nodes.Count; i++)
            {
                if(this[i.ToString()].Side.Equals(eSideInGraph.RIGHT))
                {
                    if (!SharedData.Instance.GetShiftById((this[i.ToString()] as DataNode).Data).IsFull())
                    {
                        this[i.ToString()].SetWeightOfNaighbour("t", 1);
                    }
                    else
                    {

                        // throw exception : this shift ((this[i.ToString()] as DataNode).Data as Shift).ToString() is full;
                        //Console.WriteLine("The shift {0} is full ", ((this[i.ToString()] as DataNode).Data as Shift).ToString());
                    }
                }
            }
            this["s"].SetWeightOfAllNaighbours(1);
        }

        public List<DataNode> GetAllNodesWithNeighboursWeightBiggerThenZero()
        {
            List<DataNode> list = new List<DataNode>();
            foreach (DataNode node in m_Nodes)
            {
                if (node.Side.Equals(eSideInGraph.LEFT))
                {
                    if (node.IsHaveNeighboursWithPositiveWeight())
                    {
                        list.Add(node);
                    }
                }
            }
            return list;
        }

        public List<DataNode> GetAllNodesWithNeighboursWeight(int weight)
        {
            List<DataNode> list = new List<DataNode>();
            foreach (DataNode node in m_Nodes)
            {
                if (node.Side.Equals(eSideInGraph.LEFT))
                {
                    if (node.IsHaveNeighboursWithWeight(weight))
                    {
                        list.Add(node);
                    }
                }
            }
            return list;
        }

        // DEBUG - REMOVE
        public void Print()
        {
            System.Console.WriteLine(m_Source.ToString());
            foreach (Node node in m_Nodes)
            {
                System.Console.WriteLine(node.ToString());
            }
            System.Console.WriteLine(m_Target.ToString());
        }

    }
}
